/*
 * $Id: DefaultThrowableFormatter.java 101 2013-01-14 12:51:21Z vladimirow@mail.ru $
 * $HeadURL: http://ultralog.googlecode.com/svn/ultralog/tags/release-0.1/src/java/com/google/code/ultralog/formatter/def/DefaultThrowableFormatter.java $
 *
 * This file is a part of ultralog project (http://code.google.com/p/ultralog/).
 * Released under New BSD License.
 */
package com.google.code.ultralog.formatter.def;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;

import com.google.code.ultralog.formatter.ReferenceFormatter;
import com.google.code.ultralog.utils.CharacterOutputStream;

/**
 * Default implementation of {@link ReferenceFormatter} that formats Throwable
 * objects.
 *
 * @author Mikhail Vladimirov
 */
public class DefaultThrowableFormatter
    implements ReferenceFormatter <Throwable>
{
    /**
     * Singleton instance of the formatter.
     */
    public final static DefaultThrowableFormatter INSTANCE =
        new DefaultThrowableFormatter ();

    private final static ThreadLocal <CharacterOutputStreamPrintWriter>
        PRINT_WRITER = new ThreadLocal <CharacterOutputStreamPrintWriter> ()
        {
            @Override
            protected CharacterOutputStreamPrintWriter initialValue ()
            {
                return new CharacterOutputStreamPrintWriter ();
            }
        };

    private DefaultThrowableFormatter ()
    {
        // Do nothing
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void format (Throwable value, CharacterOutputStream output)
    {
        if (output == null)
            throw new IllegalArgumentException ("Output is null");

        if (value == null)
            output.write ("null", 0, 4);
        else
        {
            CharacterOutputStreamPrintWriter printWriter =
                 PRINT_WRITER.get ().initialize (output);

            value.printStackTrace (printWriter);

            printWriter.flush ();
        }
    }

    private static class CharacterOutputStreamWriter extends Writer
    {
        private CharacterOutputStream output;
        private boolean eolSuppressed;

        public CharacterOutputStreamWriter initialize (
            CharacterOutputStream output)
        {
            if (output == null)
                throw new IllegalArgumentException ("Output is null");

            this.output = output;
            eolSuppressed = false;

            return this;
        }

        @Override
        public void write (int c) throws IOException
        {
            if (eolSuppressed)
            {
                if (c != '\n')
                {
                    output.write ('\n', (char)c);
                    eolSuppressed = false;
                }
                else
                    output.write ('\n');
            }
            else
            {
                if (c != '\n')
                    output.write ((char)c);
                else eolSuppressed = true;
            }
        }

        @Override
        public void write (char [] cbuf) throws IOException
        {
            if (cbuf == null)
                throw new IllegalArgumentException ("Cbuf is null");

            write (cbuf, 0, cbuf.length);
        }

        @Override
        public void write (char [] cbuf, int off, int len) throws IOException
        {
            if (cbuf == null)
                throw new IllegalArgumentException ("Cbuf is null");

            if (off < 0)
                throw new IllegalArgumentException ("Off (" + off + ") < 0");

            if (len < 0)
                throw new IllegalArgumentException ("Len (" + len + ") < 0");

            int cbufLength = cbuf.length;

            if (off + len > cbufLength)
                throw new IllegalArgumentException (
                    "Off (" + off + ") + len (" + len + ") > cbuf length (" +
                    cbufLength + ")");

            if (len > 0)
            {
                if (eolSuppressed)
                {
                    output.write ('\n');
                    eolSuppressed = false;
                }

                if (cbuf [off + len - 1] != '\n')
                    output.write (cbuf, off, len);
                else
                {
                    output.write (cbuf, off, len - 1);
                    eolSuppressed = true;
                }
            }
        }

        @Override
        public void write (String str) throws IOException
        {
            if (str == null)
                throw new IllegalArgumentException ("Str is null");

            write (str, 0, str.length ());
        }

        @Override
        public void write (String str, int off, int len) throws IOException
        {
            if (str == null)
                throw new IllegalArgumentException ("Str is null");

            if (off < 0)
                throw new IllegalArgumentException ("Off (" + off + ") < 0");

            if (len < 0)
                throw new IllegalArgumentException ("Len (" + len + ") < 0");

            int strLength = str.length ();

            if (off + len > strLength)
                throw new IllegalArgumentException (
                    "Off (" + off + ") + len (" + len + ") > str length (" +
                            strLength + ")");

            if (len > 0)
            {
                if (eolSuppressed)
                {
                    output.write ('\n');
                    eolSuppressed = false;
                }

                if (str.charAt (off + len - 1) != '\n')
                    output.write (str, off, len);
                else
                {
                    output.write (str, off, len - 1);
                    eolSuppressed = true;
                }
            }
        }

        @Override
        public Writer append (CharSequence csq) throws IOException
        {
            if (csq == null)
                throw new IllegalArgumentException ("Csq is null");

            return append (csq, 0, csq.length ());
        }

        @Override
        public Writer append (CharSequence csq, int start, int end)
                throws IOException
        {
            if (csq == null)
                throw new IllegalArgumentException ("Str is null");

            if (start < 0)
                throw new IllegalArgumentException ("Start (" + start + ") < 0");

            if (end < start)
                throw new IllegalArgumentException (
                    "End (" + end + ") < start (" + start + ")");

            int csqLength = csq.length ();

            if (end > csqLength)
                throw new IllegalArgumentException (
                    "End (" + end + ") > csq length (" +
                            csqLength + ")");

            int off = start;
            int len = end - start;


            if (len > 0)
            {
                if (eolSuppressed)
                {
                    output.write ('\n');
                    eolSuppressed = false;
                }

                if (csq.charAt (off + len - 1) != '\n')
                    output.write (csq, off, len);
                else
                {
                    output.write (csq, off, len - 1);
                    eolSuppressed = true;
                }
            }

            return this;
        }

        @Override
        public Writer append (char c) throws IOException
        {
            write (c);

            return this;
        }

        @Override
        public void flush () throws IOException
        {
            // Do nothing
        }

        @Override
        public void close () throws IOException
        {
            // Do nothing
        }
    }

    private static class CharacterOutputStreamPrintWriter extends PrintWriter
    {
        private final CharacterOutputStreamWriter writer;

        public CharacterOutputStreamPrintWriter ()
        {
            this (new CharacterOutputStreamWriter ());
        }

        private CharacterOutputStreamPrintWriter (
            CharacterOutputStreamWriter writer)
        {
            super (writer);

            if (writer == null)
                throw new IllegalArgumentException ("Writer is null");

            this.writer = writer;
        }

        public CharacterOutputStreamPrintWriter initialize (
            CharacterOutputStream output)
        {
            if (output == null)
                throw new IllegalArgumentException ("Output is null");

            this.writer.initialize (output);

            return this;
        }
    }
}
